unit PMDParse;

interface

uses
  System.Classes,
  System.SysUtils;


type
{$ALIGN 1}

  TFloat3 = record
    x,y,z: single;
  end;

  TNormVec3 = record
    nx,ny,nz: single;
  end;

  TUV = record
    u,v: single
  end;

  TVertex = record
    Pos : TFloat3;
    NormalVec: TNormVec3;
    uv: TUV;  // (3 + 3 + 2) * 4 = 32
    BoneNum: array[0..1] of Word; // 2 * 2 = 4
    BoneWeight: Byte; //  1
    EdgeFlag: Byte;   // 1  = 38
  end;

  TPMDStream = class(TMemoryStream)
  type
    TMagicString = string[3];
  private
    FMagicID: TMagicString;
    FVersion: Single;
    FModelName: string;
    FComment: string;

    FVertexCount: LongWord;
    Vertexs: array of TVertex;

    FFaceVertCount: LongWord;
    FaceVerts: array of Word;

    function GetVertex(Index: LongWord): TVertex; Inline;
    function GetFaceVert(Index: LongWord): Word; Inline;
  protected
    procedure Parse;
  public
    constructor Create(const PMDFilename: string);

    function FullDump: string;

    property Magic : TMagicString read FMagicID;
    property Version: single read FVersion;
    property ModelName: string read FModelName;
    property Comment: string read FComment;

    property VertexCount: LongWord read FVertexCount;
    property Vertex[Index:LongWord]: TVertex read GetVertex;

    property FaceVertCount: LongWord read FFaceVertCount;
    property FaceVert[Index:LongWord]: Word read GetFaceVert;
  end;

implementation

{ TPMDStream }

constructor TPMDStream.Create(const PMDFilename: string);
begin
  LoadFromFile(PMDFilename);
  Parse;
end;

function TPMDStream.GetVertex(Index: LongWord): TVertex;
begin
  Result := Vertexs[Index];
end;

function TPMDStream.GetFaceVert(Index: LongWord): Word;
begin
  Result := FaceVerts[Index];
end;

procedure TPMDStream.Parse;

  procedure ParseHeader;
  type
    TPMDHeader = record
      magic: array[0..2] of byte;           //  3 +
      version: single;                      //  4 +
      model_name: array[0..19] of AnsiChar; // 20 +
      comment: array[0..255] of AnsiChar;   //256 = 283
    end;
  var
    PMDHeader: TPMDHeader;
  begin
    Assert( 283 = sizeof(TPMDHeader));
    Read(PMDHeader, sizeof(TPMDHeader));

    FMagicID[0] := AnsiChar(3);
    FMagicID[1] := AnsiChar(PMDHeader.magic[0]);
    FMagicID[2] := AnsiChar(PMDHeader.magic[1]);
    FMagicID[3] := AnsiChar(PMDHeader.magic[2]);

    FVersion := PMDHeader.version;

    FModelName := PMDHeader.model_name;
    FComment := PMDHeader.comment;
  end;

  procedure ParseVertexList;
  var
    I: LongWord;
  begin
    Read(FVertexCount, sizeof(FVertexCount));
    SetLength(Vertexs, FVertexCount);

    I := 0;
    Assert(38 = sizeof(TVertex));
    while I < VertexCount do
    begin
      Read(Vertexs[I], sizeof(TVertex));
      Inc(I);
    end;
  end;

  procedure ParseFaceList;
  var
    I: LongWord;
  begin
    Read(FFaceVertCount , sizeof(FFaceVertCount));
    SetLength(FaceVerts, FaceVertCount);

    assert(FaceVertCount mod 3 = 0);
    I := 0;
    while I < FaceVertCount do
    begin
      Read(FaceVerts[I], sizeof(Word));
      Inc(I);
    end;
  end;

begin
  ParseHeader;
  ParseVertexList;
  ParseFaceList;
end;

function TPMDStream.FullDump: string;

  procedure AppendLn(const F: string; const Args: array of const);
  var
    s : String;
  begin
    s := Format(F, Args);
    if s <> '' then
      Result := Result + s + sLineBreak;
  end;

var
  L : LongWord;
begin
  Result := '';
  AppendLn('Magic:%s', [Magic]);
  AppendLn('Version:%f', [Version]);
  AppendLn('Model Name:[%s]', [ModelName]);
  AppendLn('Comment:[%s]', [Comment]);

  AppendLn('Number of Vertex: %d', [VertexCount]);

//  for L := 0 to VertexCount-1 do with Vertex[L] do
//  begin
//    AppendLn('%d:', [L]);
//    AppendLn(' Pos:(%f,%f,%f)', [Pos.x, Pos.y, Pos.z]);
//    AppendLn('  NV:(%f,%f,%f)', [NormalVec.nx, NormalVec.ny, NormalVec.nz]);
//    AppendLn('  UV:(%f,%f)', [uv.u, uv.v]);
//    AppendLn(' BoneNum:%d/%d', [BoneNum[0], BoneNum[1]]);
//    AppendLn(' BoneWeight:%d', [BoneWeight]);
//    AppendLn(' EdgeFlag:%d', [EdgeFlag]);
//  end;

  AppendLn('Number of FaceVert: %d', [FaceVertCount]);
//  for L := 0 to FaceVertCount -1 do with FaceVert[L] do
  L := 0;
  while L < FaceVertCount do
  begin
    AppendLn('%d: %d %d %d', [L, FaceVerts[L], FaceVerts[L+1], FaceVerts[L+2] ]);
    Inc(L, 3);
  end;

end;

end.
